﻿// Copyright 2011 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;

namespace Microsoft.Data.Services.Client
{
    class SimpleWebDiscoveryInspector : IDispatchMessageInspector
    {
        // Assume utf-8, note that Data Services supports
        // charset negotation, so this needs to be more
        // sophisticated (and per-request) if clients will 
        // use multiple charsest
        private static Encoding encoding = Encoding.UTF8;

        private IDictionary<string, string[]> endpoints;

        private const string swdPath = "/$wellknown/simple-web-discovery";

        public SimpleWebDiscoveryInspector(IDictionary<string, string[]> endpoints)
            : base()
        {
            this.endpoints = endpoints;
        }

        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            if (request.Properties.ContainsKey("UriTemplateMatchResults"))
            {
                HttpRequestMessageProperty httpmsg = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
                UriTemplateMatch match = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];

                string pathAfterSvc = match.RequestUri.AbsolutePath.Replace(match.BaseUri.AbsolutePath, "");
                if (pathAfterSvc.Equals(swdPath))
                {
                    httpmsg.Headers["Accept"] = "application/json";

                    string service = match.QueryParameters["service"];
                    if (this.endpoints.ContainsKey(service))
                    {
                        return this.endpoints[service];
                    }
                }
            }
            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            if (correlationState != null && correlationState is string[])
            {
                string[] addresses = correlationState as string[];
                string content = "{ \"locations\" : " + ToJsonArray(addresses) + " }";

                Message newreply = Message.CreateMessage(MessageVersion.None, "", new Writer(content));
                newreply.Properties["WebBodyFormatMessageProperty"] = new WebBodyFormatMessageProperty(WebContentFormat.Raw);

                HttpResponseMessageProperty prop = (HttpResponseMessageProperty)reply.Properties["httpResponse"];
                prop.StatusCode = System.Net.HttpStatusCode.OK;
                newreply.Properties["httpResponse"] = prop;

                reply = newreply;
            }
        }

        private string ToJsonArray(string[] array)
        {
            string content = "[ ";
            if (array.Length > 0)
            {
                content += "\"" + array[0] + "\"";
                for (int i = 1; i < array.Length; ++i)
                {
                    content += ", \"" + array[i] + "\"";
                }
            }
            return content + " ]";
        }
        
        #endregion

        class Writer : BodyWriter
        {
            private string content;

            public Writer(string content)
                : base(false)
            {
                this.content = content;
            }

            protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
            {
                writer.WriteStartElement("Binary");
                byte[] buffer = SimpleWebDiscoveryInspector.encoding.GetBytes(this.content);
                writer.WriteBase64(buffer, 0, buffer.Length);
                writer.WriteEndElement();
            }
        }
    }

    // Simply apply this attribute to a DataService-derived class to allow
    // simple web discovery support in that service
    [AttributeUsage(AttributeTargets.Class)]
    public class SimpleWebDiscoveryBehaviorAttribute : Attribute, IServiceBehavior
    {
        private IDictionary<string, string[]> endpoints;

        public SimpleWebDiscoveryBehaviorAttribute(Type type) : base()
        {
            Endpoints endpointDictionary = (type.GetConstructor(new Type[] { }).Invoke(new object[] { })) as Endpoints;
            if (endpointDictionary == null)
            {
                throw new ArgumentException("Argument must be a subclass of the Endpoints class.");
            }
            endpoints = new Dictionary<string, string[]>();
            foreach (KeyValuePair<string, List<string>> p in endpointDictionary.EndpointDictionary)
            {
                this.endpoints[p.Key] = p.Value.ToArray();
            }
        }

        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(
            ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase,
            System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.MessageInspectors.Add(new SimpleWebDiscoveryInspector(this.endpoints));
                }
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }

}